//
//  ADSRGenerator.m
//  CocoaSynth
//
//  Created by Pierre DUCHENE on 17/02/10.
//  Copyright 2010 Pierre DUCHENE. All rights reserved.
//

#import "ADSRGenerator.h"
#import "SampleProtocol.h"
#import "Sample.h"
#import "System.h"
#import "SignalVisualizer.h"

@interface ADSRGenerator (Helpers)
- (BOOL)needChangeState:(NSArray*)gateSample;
@end

@implementation ADSRGenerator

#pragma mark -
#pragma mark Accessors

@synthesize module;

#pragma mark -
#pragma mark Memory Management

- (id)init
{
	if (self = [super init]) {
		module = nil;
		isRunning = NO;
		stateHasChanged = NO;
		sustainDuration = 0;
	}
	
	return self;
}

- (void)dealloc
{
	[module release];
	module = nil;
	
	[super dealloc];
}

#pragma mark -
#pragma mark ComputingStrategyProtocol

- (id<SampleProtocol>)execute
{
	id<SampleProtocol> result = [[Sample alloc] init];
	
	// Get Module infos
	float attack = [module attack];
	float decay = [module decay];
	float sustain = [module sustain];
	float release = [module releaseValue];
	NSArray* gateSample = [[module inputSample] voltages];
	
	// Get system infos
	System* system = [System sharedSystem];
	float time = [system time];
	float sampleDuration = [system sampleDuration];
	float ADSRPosition = time - [system ADSRStartTime];
	float delta_time =  1 / [system sampleRate]; // Time between two values
	float a_max = 1.f; // Amplitude maximum
	
	// Check input to know if it's necessary to change ADSR State: start ADSR, continue Sustain or stop sustain
	BOOL change;
	if (gateSample != nil) {
		change  = [self needChangeState:gateSample];
	}
	if (change) {
		isRunning = !isRunning;
		stateHasChanged = YES;
		if (isRunning) {
			// Starting ADSR
			[system setADSRStartTime:time];
			sustainDuration = 0;
		}
		else {
			
		}
	}
	else {
		stateHasChanged = NO;
		if (isRunning) {
			sustainDuration += sampleDuration;
		}
	}


	
	// Create output array
	NSMutableArray* outputValues = [[NSMutableArray alloc] init];
	// Variable to compute affine function formula y = ax + b
	float y;
	float x;
	float a;
	float b;
	/*/
	for (int i = 0; i < [system sampleSize]; i++) {
		// We set the position in the ADSR
		x = ADSRPosition;
		ADSRPosition += delta_time;
		if (stateHasChanged && !isRunning) {
			// RELEASE
			NSLog(@"Release");
			a = -sustain / release;
			b = sustain - (a * (attack + decay + sustain));
			y = a * x + b;
		}
		else if (stateHasChanged && isRunning) {
			//ATTACK
			NSLog(@"Attack");
			y = (a_max / attack) * x;
		}
		else if (!stateHasChanged && isRunning) {
			// SUSTAIN or end of ATTACK or end of DECAY
			if(ADSRPosition < attack) {
				// Finish ATTACK
				NSLog(@"Attack");
				y = (a_max / attack) * x;
			}
			else if (ADSRPosition < attack + decay) {
				// DECAY
				NSLog(@"Decay");
				a = (sustain - a_max) / decay;
				b = a_max - (a * attack);
				y = a * x + b;
			}
			else {
				// SUSTAIN
				NSLog(@"Sustain");
				y = sustain;
			}
		}
		else if (!stateHasChanged && !isRunning) {
			// End of Release or IDLE
			NSLog(@"Idle");
			y = 0;
		}
		[outputValues insertObject:[NSNumber numberWithFloat:y] atIndex:i];
	}
	
	[[SignalVisualizer sharedSignalVisualizer] addSample:outputValues];
	
	//*/
	//*
	for (int i = 0; i < [system sampleSize]; i++) {
		// We set the position in the ADSR
		x = ADSRPosition;
		ADSRPosition += delta_time; 
		// We need to know at which section we are in the ADSR envelope
		if (ADSRPosition < 0) {
			// Before start
			y = 0;
		}
		else if(ADSRPosition < attack) {
			// Still in attack part
			y = (a_max / attack) * x;
		}
		else if (ADSRPosition < attack + decay) {
			// Decay part
			a = (sustain - a_max) / decay;
			b = a_max - (a * attack);
			y = a * x + b;
		}
		else if (ADSRPosition < attack + decay + sustainDuration) {
			// Sustain part
			y = sustain;
		}
		else if (ADSRPosition < attack + decay + sustainDuration + release) {
			// Release part
			a = -sustain / release;
			b = sustain - (a * (attack + decay + sustainDuration));
			y = a * x + b;
		}
		else {
			y = 0;
		}
		[outputValues insertObject:[NSNumber numberWithFloat:y] atIndex:i];
	}
	//*/
	
	//[[SignalVisualizer sharedSignalVisualizer] addSample:outputValues];
	
	[result setVoltages:[NSArray arrayWithArray:outputValues]];
	[outputValues release];
	return [result autorelease];
}

#pragma mark -
#pragma mark Helpers Methods
/*!
    @method     needStart:
    @abstract   This method return YES if ADSR need to start his envelope
    @discussion The mechanism is to check in the input sample if sign value changed or not
				If all sample values are zero value, then the method return YES
	@result		YES if ADSR need to start NO else
*/
- (BOOL)needChangeState:(NSArray*)gateSample
{
	BOOL result = NO;
	
	int i = 0;

	float firstValues = 0;
	for (int i =0; i < 3; i++) {
		firstValues = (firstValues + [[gateSample objectAtIndex:i] floatValue]) / 2;
	}
	float preValue = [[gateSample objectAtIndex:0] floatValue];
	float currentValue = 0;
	
	while ((i < [gateSample count]) && !result) {
		currentValue = [[gateSample objectAtIndex:i] floatValue];
		
		result = ((preValue < 0) & (currentValue > 0));
		result |= ((preValue > 0) & (currentValue < 0));
		result |= ((firstValues == 0) && (currentValue != 0));
		i++;
	}
	return result;
}

@end

